Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit b7fa09df17b844f9dc15f61d0f057d2ad5b1d352


Parents : 7f83b1c
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-06-16T18:50:16-05:00

feat(nomadnetwork): improve tab management with existing tab focus and new tab creation options

Changes
Diff

diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
index 1eee9116..f3b86e55 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkBrowser.vue
@@ -63,6 +63,7 @@ import NomadNetworkPage from "./NomadNetworkPage.vue";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import GlobalState from "../../js/GlobalState";
import { loadNomadTabs, saveNomadTabs } from "../../js/browserLayoutStore";
+import LinkUtils from "../../js/LinkUtils";
export default {
name: "NomadNetworkBrowser",
@@ -171,8 +172,24 @@ export default {
return id;
},
onOpenNode(payload) {
+ const destinationHash = payload?.destinationHash || "";
+ const forceNewTab = payload?.forceNewTab === true;
+
+ if (destinationHash && !forceNewTab) {
+ const existing = this.tabs.find((tab) => tab.destinationHash === destinationHash);
+ if (existing) {
+ if (payload?.title) {
+ existing.title = payload.title;
+ }
+ if (payload?.activate !== false) {
+ this.selectTab(existing.id);
+ }
+ return;
+ }
+ }
+
this.addTab(
- payload?.destinationHash || "",
+ destinationHash,
payload?.pagePath || null,
payload?.title || null,
payload?.activate !== false
@@ -255,13 +272,22 @@ export default {
return false;
}
- this.tabs = saved.tabs.map((tab) => ({
- id: this.nextTabId++,
- destinationHash: typeof tab.destinationHash === "string" ? tab.destinationHash : "",
- initialPath: typeof tab.path === "string" ? tab.path : null,
- path: typeof tab.path === "string" ? tab.path : null,
- title: typeof tab.title === "string" ? tab.title : null,
- }));
+ this.tabs = saved.tabs
+ .map((tab) => ({
+ id: this.nextTabId++,
+ destinationHash:
+ typeof tab.destinationHash === "string" && /^[0-9a-fA-F]{32}$/.test(tab.destinationHash)
+ ? tab.destinationHash
+ : "",
+ initialPath: this.sanitizeNomadTabPath(tab.path),
+ path: this.sanitizeNomadTabPath(tab.path),
+ title: typeof tab.title === "string" ? tab.title : null,
+ }))
+ .filter((tab) => !this.isExternalNomadTabPath(tab.path));
+
+ if (this.tabs.length === 0) {
+ return false;
+ }
const activeIndex =
Number.isInteger(saved.activeIndex) && saved.activeIndex >= 0 && saved.activeIndex < this.tabs.length
@@ -281,12 +307,24 @@ export default {
this.syncRoute();
return true;
},
+ sanitizeNomadTabPath(path) {
+ if (typeof path !== "string" || path.length === 0) {
+ return null;
+ }
+ if (LinkUtils.httpUrlHrefOrNull(path.trim())) {
+ return null;
+ }
+ return path;
+ },
+ isExternalNomadTabPath(path) {
+ return typeof path === "string" && LinkUtils.httpUrlHrefOrNull(path.trim()) != null;
+ },
persistTabs() {
const activeIndex = this.tabs.findIndex((tab) => tab.id === this.activeTabId);
saveNomadTabs({
tabs: this.tabs.map((tab) => ({
destinationHash: tab.destinationHash || "",
- path: tab.path || null,
+ path: this.sanitizeNomadTabPath(tab.path),
title: tab.title || null,
})),
activeIndex: activeIndex < 0 ? 0 : activeIndex,

diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
index 700835da..0ef69e0f 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
@@ -407,6 +407,7 @@
nomadRenderedShellFullBleed
? 'p-0 bg-transparent min-h-full text-gray-900 dark:text-gray-100'
: 'p-3 bg-black text-white',
+ nomadShellDark ? 'nomad-shell-dark' : '',
]"
:style="nodeContainerShellStyle"
@click.capture="onElementClick"
@@ -858,6 +859,28 @@ export default {
}
return { background: this.pageShellBackground };
},
+ nomadShellDark() {
+ if (!this.nomadRenderedShellFullBleed) {
+ return true;
+ }
+ const bg = this.pageShellBackground;
+ if (!bg || typeof bg !== "string") {
+ return false;
+ }
+ const lower = bg.toLowerCase().replace(/\s/g, "");
+ if (lower === "#000" || lower === "#000000" || lower === "black" || lower === "rgb(0,0,0)") {
+ return true;
+ }
+ const rgbMatch = lower.match(/^rgba?\((\d+),(\d+),(\d+)/);
+ if (rgbMatch) {
+ const r = Number(rgbMatch[1]);
+ const g = Number(rgbMatch[2]);
+ const b = Number(rgbMatch[3]);
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ return luminance < 0.45;
+ }
+ return false;
+ },
nomadRenderedShellFullBleed() {
if (!this.nodePagePath || this.isShowingNodePageSource) {
return false;
@@ -1175,6 +1198,7 @@ export default {
pagePath,
title,
activate: navOptions.activate !== false,
+ forceNewTab: navOptions.forceNewTab === true,
});
},
onElementClick(event) {
@@ -1189,6 +1213,18 @@ export default {
return;
}
+ const externalAnchor = event.target.closest("a[href]");
+ if (externalAnchor && !externalAnchor.classList.contains("nomadnet-link")) {
+ const href = externalAnchor.getAttribute("href");
+ const httpHref = href ? LinkUtils.httpUrlHrefOrNull(href.trim()) : null;
+ if (httpHref) {
+ event.preventDefault();
+ event.stopPropagation();
+ window.open(httpHref, "_blank", "noopener,noreferrer");
+ return;
+ }
+ }
+
const fragAnchor = event.target.closest("a[href]");
if (
fragAnchor &&
@@ -2675,9 +2711,29 @@ pre.text-wrap > div > :last-child {
border-radius: 0;
background: transparent;
color: inherit;
+ caret-color: currentColor;
+ -webkit-text-fill-color: currentColor;
box-sizing: content-box;
}
+.nodeContainer.bg-black input[type="text"],
+.nodeContainer.bg-black input[type="password"],
+.nodeContainer.bg-black textarea {
+ color: #f3f4f6 !important;
+ caret-color: #f3f4f6 !important;
+ -webkit-text-fill-color: #f3f4f6 !important;
+ border-bottom-color: #f3f4f6 !important;
+}
+
+.nodeContainer.nomad-shell-dark input[type="text"],
+.nodeContainer.nomad-shell-dark input[type="password"],
+.nodeContainer.nomad-shell-dark textarea {
+ color: #f3f4f6 !important;
+ caret-color: #f3f4f6 !important;
+ -webkit-text-fill-color: #f3f4f6 !important;
+ border-bottom-color: #f3f4f6 !important;
+}
+
.nomad-markdown-host {
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}

diff --git a/tests/frontend/NomadNetworkBrowser.test.js b/tests/frontend/NomadNetworkBrowser.test.js
index 381313ef..c3d6ed9f 100644
--- a/tests/frontend/NomadNetworkBrowser.test.js
+++ b/tests/frontend/NomadNetworkBrowser.test.js
@@ -224,6 +224,41 @@ describe("NomadNetworkBrowser.vue", () => {
expect(wrapper.vm.activeTab.title).toBe("Remote Node");
});
+ it("onOpenNode focuses an existing tab when the node is already open", () => {
+ const wrapper = mountBrowser();
+ const existingHash = "g".repeat(32);
+ const existingTabId = wrapper.vm.addTab(existingHash, "/page/index.mu", "Remote Node");
+ wrapper.vm.addTab("h".repeat(32));
+ expect(wrapper.vm.tabs).toHaveLength(3);
+ expect(wrapper.vm.activeTabId).not.toBe(existingTabId);
+
+ wrapper.vm.onOpenNode({
+ destinationHash: existingHash,
+ pagePath: "/page/other.mu",
+ title: "Remote Node",
+ activate: true,
+ });
+
+ expect(wrapper.vm.tabs).toHaveLength(3);
+ expect(wrapper.vm.activeTabId).toBe(existingTabId);
+ expect(wrapper.vm.activeTab.destinationHash).toBe(existingHash);
+ });
+
+ it("onOpenNode still creates a duplicate tab when forceNewTab is set", () => {
+ const wrapper = mountBrowser();
+ const existingHash = "g".repeat(32);
+ wrapper.vm.addTab(existingHash, "/page/index.mu", "Remote Node");
+ expect(wrapper.vm.tabs).toHaveLength(2);
+ wrapper.vm.onOpenNode({
+ destinationHash: existingHash,
+ pagePath: "/page/other.mu",
+ title: "Remote Node",
+ activate: true,
+ forceNewTab: true,
+ });
+ expect(wrapper.vm.tabs).toHaveLength(3);
+ });
+
it("onOpenNode can open a background tab without switching focus", () => {
const wrapper = mountBrowser();
const first = wrapper.vm.activeTabId;


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────